#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/boot.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#define F_CPU F_OSC
#include <util/delay.h>

#include "uart.h"
//#include "../firmware/usbn2mc.h"
#include "usbn2mc.h"
#include "../usbn2mc/tiny/usbnapi.h"
#include "../usbn2mc/usbn960xreg.h"

#define DEBUG 1

#if DEBUG
  #define debug(s)        uart_write(s)
  #define debug_hex(n)    uart_sendhex(n)
#else
  #define debug(s)        {}
  #define debug_hex(n)    {}
#endif

//////////////////////////////////////////////////////////////////////////////

// EEMEM wird bei aktuellen Versionen der avr-lib in eeprom.h definiert
// hier: definiere falls noch nicht bekannt ("alte" avr-libc)
#ifndef EEMEM
// alle Textstellen EEMEM im Quellcode durch __attribute__ ... ersetzen
#define EEMEM  __attribute__ ((section (".eeprom")))
#endif

//////////////////////////////////////////////////////////////////////////////

// __heap_start is declared in the linker script
//static uint8_t eeFooByte EEMEM = 1;

//////////////////////////////////////////////////////////////////////////////

/* pointer to the beginning of application code */
static void (*avrupdate_jump_to_app) (void) = 0x0000;

//////////////////////////////////////////////////////////////////////////////

/* external interrupt from usbn9604 */
SIGNAL (SIG_INTERRUPT4)
{
  USBNInterrupt ();
}

//////////////////////////////////////////////////////////////////////////////

/* usbn2mc tiny needs this */
void USBNInterfaceRequests (DeviceRequest * req, EPInfo * ep)
{
}

/*  wait function */
static void wait_ms (int ms)
{
  while (ms--)
    _delay_ms (1);         // max delay is 262ms / 16 MHz = 16ms
}

/* pogramm a page into flash 
 *	@page = number of page
 *	@pageblock = pointer to data
 */

static void avrupdate_program_page (uint32_t page, uint8_t *pageblock)
{
  debug("avrupdate_program_page");
	
  uint8_t sreg = SREG;
  cli ();

  eeprom_busy_wait ();

  boot_page_erase (page);
  boot_spm_busy_wait ();  // Wait until the memory is erased.

  uint16_t *pageblock_w = (uint16_t *)pageblock;
  uint32_t page2 = page;
  uint8_t i = SPM_PAGESIZE / 2;

  do
  {
    boot_page_fill (page2, *pageblock_w++);

    #if SPM_PAGESIZE <= 256
      // *(uint8_t *)&page2 += 2;
      asm ("inc %A0" "\n\t" "inc %A0" "\n\t" : "=r" (page2) : "0" (page2));
    #else
      page2 += 2;
    #endif
  } while (--i > 0);

  boot_page_write (page); // Store buffer in flash page.
  boot_spm_busy_wait ();  // Wait until the memory is written.

  // Reenable RWW-section again. We need this if we want to jump back
  // to the application after bootloading.

  boot_rww_enable ();

  // Re-enable interrupts (if they were ever enabled).
  SREG = sreg;
}

//////////////////////////////////////////////////////////////////////////////

#define NONE	      0x00
#define STARTAPP      0x01
#define WRITEPAGE     0x02
#define GETVERSION    0x03
#define SETVERSION    0x04
#define STOPPROGMODE  0x05

//////////////////////////////////////////////////////////////////////////////

static volatile uint8_t need_flash = 0;

static uint8_t  state = NONE;
static uint16_t page_addr;
static uint8_t  collect128 = 0;
static uint8_t  pageblock[SPM_PAGESIZE];

#define MAX_N	(SPM_PAGESIZE / 64)

//////////////////////////////////////////////////////////////////////////////

// start programm from application sector
static void avrupdate_start_app (void)
{
  debug("avrupdate_start_app()\r\n");

  USBNWrite (FIFORXC1, FLUSH);
  USBNWrite (MCNTRL, SRST);  // clear all usb registers

  cli ();
  MCUCR = (1 << IVCE);  // enable change of IVSEL
  MCUCR = 0;            // switch Interrupts vectors to Application Section

  avrupdate_jump_to_app (); // Jump to application sector
}

static void avrupdate_cmd (uint8_t *buf)
{
  debug("avrupdate_cmd()\r\n");

  // check state first ist 
  switch (state)
  {
    case STOPPROGMODE:
      debug("STOPPROGMODE\r\n");
      //eeprom_write_byte(&eeFooByte,0x00);
      //wait_ms(10);
      break;

    case WRITEPAGE:
      debug("WRITEPAGE\r\n");
      {
	uint8_t *p = pageblock + 64 * collect128;
	for (uint8_t i = 64; i-- > 0; )
	  *p++ = *buf++;
      }
      collect128++;

      if (collect128 == MAX_N)
      {
	// write page
	uint32_t page_addr_w = page_addr * SPM_PAGESIZE;
	avrupdate_program_page (page_addr_w, pageblock);
	collect128 = 0;
	state = NONE;

	pageblock [0] = 0x55;
	pageblock [1] = 0xAA;
	*(uint16_t *) &pageblock[2] = page_addr;
	//USBNTransmitFIFO1 (4, pageblock);
      }
      break;

    default:
      debug("DEFAULT\r\n");

      state = buf[0];
      if (state == WRITEPAGE)
      {
	need_flash = 1;		// tell main to stay
	collect128 = 0;
	page_addr = *(uint16_t *) &buf[1];
      }

      if (state == STARTAPP)
      {
	avrupdate_start_app ();
	state = NONE;
      }
  }
}

// ********************************************************************
// Device descriptor
// ********************************************************************

//#define VID 0x1781
//#define PID 0x0c65

unsigned char avrupdateDevice[] =
{
  0x12,        // 18 length of device descriptor
  0x01,        // descriptor type = device descriptor 
  0x10, 0x01,  // version of usb spec. ( e.g. 1.1) 
  0x00,        // device class
  0x00,        // device subclass
  0x00,        // protocol code
  0x08,        // deep of ep0 fifo in byte (e.g. 8)
  0x81, 0x17,  // vendor id
  0x65, 0x0c,  // product id
  0x01, 0x00,  // revision id (e.g 1.02)
  0x00,        // index of manuf. string
  0x00,        // index of product string
  0x01,        // index of ser. number
  0x01         // number of configs
};

// ********************************************************************
// Configuration descriptor
// ********************************************************************

#define O_DO_RESPONSES		1

unsigned char avrupdateConf[] =
{
  0x09,	 // 9 length of this descriptor
  0x02,  // descriptor type = configuration descriptor 
  #if O_DO_RESPONSES
    7+
  #endif
  9+9+7, 0x00,// total length with first interface ... 
  0x01,  // number of interfaces
  0x01,  // number if this config. (arg for setconfig)
  0x00,  // string index for config
  0xA0,  // attrib for this configuration (bus powerded, remote wakup support)
  0x32,  // power for this configuration in * 2mA (e.g. 50mA)

  //InterfaceDescriptor
  0x09,  // 9 length of this descriptor
  0x04,  // descriptor type = interface descriptor 
  0x00,  // interface number 
  0x00,  // alternate setting for this interface 
  #if O_DO_RESPONSES
    1+
  #endif
  0x01,  // number endpoints without 0
  0x00,  // class code 
  0x00,  // sub-class code 
  0x00,  // protocoll code
  0x00,  // string index for interface
  #if O_DO_RESPONSES
    //EP1 Descriptor
    0x07,        // 7 length of ep descriptor
    0x05,        // descriptor type= endpoint
    0x82,        // endpoint address (e.g. in ep2)
    0x02,        // transfer type (bulk)
    0x40, 0x00,  // fifo size
    0x00,        // polling intervall in ms
  #endif
  //EP2 Descriptor
  0x07,        // 7 length of ep descriptor
  0x05,        // descriptor type= endpoint
  0x02,        // endpoint address (e.g. out ep2)
  0x02,        // transfer type (bulk)
  0x40, 0x00,  // fifo size
  0x00,        // polling intervall in ms
};

// HERE: decrypt ...
static const char *txt[] =
{
  "\x12\0032\0000\0000\0008\0001\0001\0000\0008\000", // Serial: "20081108"
//"\x22\003E\000m\000b\000e\000d\000d\000e\000d\000P\000r\000o\000j\000e\000c\000t\000s\000",
//"\x62\003O\000c\000t\000o\000p\000u\000s\000U\000S\000B\000 \000I\000n\000t\000e\000r\000f\000a\000c\000e\000 \000C\000o\000n\000v\000e\000r\000t\000e\000r\000 \000a\000n\000d\000 \000I\000/\000O\000 \000E\000x\000t\000e\000n\000s\000i\000o\000n\000",
};

//////////////////////////////////////////////////////////////////////////////

const uint8_t *UsbGetDeviceDescriptor (void)
{
  return avrupdateDevice;
}

//////////////////////////////////////////////////////////////////////////////

const uint8_t *UsbGetConfigDescriptor (uint8_t index)
{
  (void) index;
  return avrupdateConf;
}

//////////////////////////////////////////////////////////////////////////////

const uint8_t *UsbGetString (uint8_t index)
{
  //EP0tx.Buf = (uint8_t *) FinalStringArray[index];
  return (const uint8_t *) txt [index-1];
}

//////////////////////////////////////////////////////////////////////////////

int main (void)
{
  #if DEBUG
    uart_init();
  #endif
  debug("main()\r\n");

  #if defined(MT_BOOTLOADER_ADDRESS)
    cli ();		// probably no need ...
    MCUCR = (1 << IVCE);  // enable change of IVSEL
    MCUCR = (1 << IVSEL); // switch Interrupts vectors to Boot Section
  #endif

  // blinken
  DDRC  = 0xFF;
  DDRB |= 0x80;

  for (uint8_t i = 0; i < 10; i++)
  {
    PORTB ^= 0x80;
    wait_ms (100); // timer is still slow
  }

  // bootloader application starts here
  USBNInit (avrupdateDevice, avrupdateConf);
  USBNCallbackFIFORX1 (&avrupdate_cmd);

  sei();
  USBNInitMC ();  // has sei ();

  // start usb chip and activate our 16MHz clock
  USBNStart ();

  debug("bootloader now active\r\n");
  wait_ms (100);

  if (pgm_read_byte(0) == 0xFF)
    need_flash = 1;

  uint8_t countdown = 10;  // can be reset by the upload
  while (countdown--)
  {
    if (need_flash)
      break;

    // blinken
    PORTB |= 0x80;
    PORTC  = 0xF0;  // keep USBN /CS high (0x80)
    wait_ms (500);
    PORTB &= 0x7F;
    PORTC  = 0x87;
    wait_ms (500);
  }

  if (need_flash)  // loop forever, if no prog at 0 or download started
  {
    while(1)
    {
      // blinken
      PORTB |= 0x80;
      PORTC  = 0xC0;  // keep USBN /CS high (0x80)
      wait_ms (500);
      PORTB &= 0x7F;
      PORTC  = 0x81;
      wait_ms (500);
    }
  }

  avrupdate_start_app ();

  return 0;
}
